import greenfoot.Actor;
import greenfoot.GreenfootImage;
import greenfoot.World;
import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * A slider to allow a user to select a number in a given range.
 * 
 * Create a Slider by calling <code>new {@link Slider()}</code> (for the default size)
 * or <code>new {@link Slider(int, int)}</code> (for a custom size).
 * Set teh maximum value that the user can select by calling
 * {@link setMaximumValue(int)}, set the initial value by calling
 * {@link setValue(int)} and add it to the world by calling the
 * addObject(Actor, int, int) method on the World.
 * 
 * You can also set how the slider should display by calling the other methods.
 * This <i>should</i> be done before adding the slider to the world, but does not need to be
 * 
 * @author mBread
 * @version 29-01-2008
 */
public class Slider extends Actor {
	//The background & foregreound images. See updateImage()
	private GreenfootImage background, foreground;

	//The height, width of the foreground and height of the foreground
	private int height, fgWidth = 9, fgHeight = 10;
	//The maximum value, width in pixels, width of the selectable area, width of the value label,
	//the padding either side of the selectable area, the x-coordinate of the selectable area
	//and the x-coordinate of the right-hand side of the selectable area
	private int maxValue, actualWidth, valueWidth, valueLabelWidth, padding,
			valueX, valueRight;
	//The number of major and minor sections
	private int majorSections, minorSections;
	//Flags meaning: display the value label, show the value as a percentage, re-draw the images
	private boolean showValue = false, showPercentage = true,
			imagesInvalid = true;
	//How wide each number is in the selectable value
	private float pixelsPerValue;
	//The current value is
	private int value;

	/** 
	 * <!-- begin-UML-doc -->
	 * <!-- end-UML-doc -->
	 * @generated "UML to Java (com.ibm.xtools.transform.uml2.java5.internal.UML2JavaTransform)"
	 */
	public boolean showingValue;

	/** 
	 * <!-- begin-UML-doc -->
	 * <!-- end-UML-doc -->
	 * @generated "UML to Java (com.ibm.xtools.transform.uml2.java5.internal.UML2JavaTransform)"
	 */
	public boolean showingPercentage;

	/** 
	 * <!-- begin-UML-doc -->
	 * <!-- end-UML-doc -->
	 * @generated "UML to Java (com.ibm.xtools.transform.uml2.java5.internal.UML2JavaTransform)"
	 */
	public int maximumValue;

	/**
	 * Create a new slider with the default size. It will be 200 pixels wide by
	 * 20 pixels high.
	 */
	public Slider() {
		this(200, 20);
	}

	/**
	 * Create a new slider.
	 * 
	 * @param width The width of the slider in pixels
	 * @param height The height of th slider in pixels
	 */
	public Slider(int width, int height) {
		majorSections = 2;
		minorSections = 2;

		maxValue = 100;
		actualWidth = width;
		this.height = height;
		fgHeight = height / 2;
		fgWidth = fgHeight;
		if (fgWidth % 2 == 0) {
			fgWidth--;
		}

		setImage(new GreenfootImage(actualWidth, height));
	}

	/**
	 * This method is called by the World class when the slider is added to the
	 * world. You will not need to call this method.
	 * 
	 * @param world The world
	 */
	public void addedToWorld(World world) {
		createImages();
		updateImage();
		imagesInvalid = false;
		valueX = getX() * getWorld().getCellSize() - actualWidth / 2 + padding;
		valueRight = valueX + valueWidth;
	}

	/**
	 * This method is called by Greenfoot, and updates the slider if the user
	 * has dragged the slider.
	 */
	public void act() {
		if (Greenfoot.mouseDragged(this) || Greenfoot.mousePressed(this)) {
			MouseInfo mouseInfo = Greenfoot.getMouseInfo();
			int x = Math.max(valueX, mouseInfo.getX()
					* getWorld().getCellSize());
			x -= valueX;
			x = Math.min(x, valueRight);
			setValue(posToValue(x));
			imagesInvalid = true;
		}
		if (imagesInvalid) {
			createImages();
			updateImage();
			imagesInvalid = false;
		}
	}

	/**
	 * Set the current selected value of the slider. The display will change
	 * to represent this new value.
	 * 
	 * @param value The new value
	 */
	public void setValue(int value) {
		if (value < 0) {
			value = 0;
		}
		if (value > maxValue) {
			value = maxValue;
		}
		this.value = value;
		imagesInvalid = true;
	}

	/**
	 * Get the current value.
	 * 
	 * @return The current selected value
	 */
	public int getValue() {
		return value;
	}

	/**
	 * Set whether the value should be displayed next to the slider.
	 * 
	 * If this method is called with false as the parameter then it will
	 * also set {@link shoePercentage(boolean)} will also be set to false.
	 * 
	 * @see showPercentage(boolean)
	 * @param show  true if the value should be displayed as text,
	 * false if it should not be
	 */
	public void showValue(boolean show) {
		if (showValue != show) {
			imagesInvalid = true;
		}
		showValue = show;
		if (!show) {
			if (showPercentage)
				imagesInvalid = true;
			showPercentage = false;
		}
	}

	/**
	 * Check whether the slider is set to display the value.
	 * 
	 * @see showValue(boolean)
	 * @see isShowingPercentage()
	 * @return true if the value is being displayed, false if it is not
	 */
	public boolean isShowingValue() {
		return showValue;
	}

	/**
	 * Set whether the value will be displayed next to the slider as a percentage.
	 * 
	 * This method will take precedence over {@link showValue(boolean)}. If
	 * both are set to true, the percentage is shown. If both are true and
	 * showPercentage(false) is called then the value will be shown. If both
	 * are true and showValue(false) is called then neither the value nor
	 * percentage is shown.
	 * 
	 * @see showValue(boolean)
	 * @see isShowingPercentage()
	 * @param show true if the value should be displayed as a percentage of
	 * the maximum value, false if it should not.
	 */
	public void showPercentage(boolean show) {
		if (showPercentage != show) {
			imagesInvalid = true;
		}
		showPercentage = show;
	}

	/**
	 * Check whether the slider is set to display the value as a percentage.
	 * 
	 * @see showPercentage(boolean)
	 * @see isShowingValue()
	 * @return true if the percentage is being displayed, false if it is not
	 */
	public boolean isShowingPercentage() {
		return showPercentage;
	}

	/**
	 * Set the number of major sections to be marked. Set the number of sections to
	 * zero to not display any major markers.
	 * 
	 * There will be 1 more marker than sections as there is a
	 * marker at both ends as well as between each section
	 * 
	 * @see setMinorSections(int)
	 * @param sections The number of major sections to display
	 */
	public void setMajorSections(int sections) {
		majorSections = sections;
		imagesInvalid = true;
	}

	/**
	 * Get the number of major sections that are marked.
	 * 
	 * @see setMajorSections(int)
	 * @return The number of major sections that are marked
	 */
	public int getMajorSections() {
		return majorSections;
	}

	/**
	 * Set the number of minor sections to be marked per major secton. Set this
	 * to zero or 1 to not display any minor markers.
	 * 
	 * There will be one less minor marker (per major section) than the number
	 * of minor sections as there is not a marker at each end due to the major
	 * markers being there.
	 * 
	 * @see setMajorSections(int)
	 * @param sections The number of minor sections to mark per major section
	 */
	public void setMinorSections(int sections) {
		minorSections = sections;
		imagesInvalid = true;
	}

	/**
	 * Get the number of minor sections that are marked.
	 * 
	 * @see setMinorSections(int)
	 * @return The number of minor sections that are marked
	 */
	public int getMinorSections() {
		return minorSections;
	}

	/**
	 * Set the maximum value that the user can select.
	 * 
	 * @param value The new maximum value
	 */
	public void setMaximumValue(int value) {
		if (value <= 0)
			return;
		maxValue = value;
		pixelsPerValue = valueWidth / (float) maxValue;
		setValue(value);
	}

	/**
	 * Get the current maximum value which the user can select
	 * 
	 * @return The maximum value
	 */
	public int getMaximumValue() {
		return maxValue;
	}

	/**
	 * Convert a pixel position (relitive to the left of the selectable area)
	 * to a selected value.
	 */
	private int posToValue(int x) {
		int v = Math.round(x / pixelsPerValue);
		return v;
	}

	/**
	 * Convert a selected value to a pixel position
	 */
	private int valueToPos(int v) {
		int p = Math.round(v * pixelsPerValue);
		return p;
	}

	/**
	 * Create the background & foreground images
	 */
	private void createImages() {
		if (showPercentage) {
			valueLabelWidth = 4 * 10;
		} else if (showValue) {
			valueLabelWidth = (int) (Math.floor(Math.log10(maxValue)) + 1) * 10;
		} else {
			valueLabelWidth = 0;
		}
		valueWidth = actualWidth - fgWidth - valueLabelWidth;
		valueWidth -= valueWidth % 2;
		padding = fgWidth / 2;
		pixelsPerValue = valueWidth / (float) maxValue;
		assert padding >= 0;

		background = new GreenfootImage(actualWidth, height);
		foreground = new GreenfootImage(fgWidth, height);
		background.drawLine(padding, height / 2, padding + valueWidth,
				height / 2);
		if (majorSections > 0) {
			float pixelsPerMarker = valueWidth / (float) majorSections;
			float pixelsPerMinor = 0;
			if (minorSections > 0)
				pixelsPerMinor = pixelsPerMarker / (float) minorSections;
			for (int i = 0; i <= majorSections; i++) {
				int x = Math.round(i * pixelsPerMarker + padding);
				background.drawLine(x, height, x, 2 * height / 3);
				for (int m = 1; m < minorSections && i < majorSections; m++) {
					int mx = Math.round(x + m * pixelsPerMinor);
					background.drawLine(mx, 5 * height / 6, mx, 4 * height / 6);
				}
			}
		}
		foreground.fillRect(0, 0, fgWidth, fgHeight);
		int pointHeight = Math.min(fgWidth / 2, height - fgHeight);
		int[] pointerXPoints = { 0, fgWidth / 2, fgWidth - 1 };
		int[] pointerYPoints = { fgHeight, pointHeight + fgHeight, fgHeight };
		foreground.fillPolygon(pointerXPoints, pointerYPoints, 3);
		if (foreground.getWidth() % 2 == 0) {
			foreground.scale(foreground.getWidth() + 1, foreground.getHeight());
		}
	}

	/**
	 * Update the position of the foreground image, and the text of the
	 * value label.
	 */
	private void updateImage() {
		GreenfootImage image = new GreenfootImage(background);
		image.drawImage(foreground, valueToPos(value), 0);
		if (showPercentage) {
			image.drawString((value * 100) / maxValue + "%", valueWidth
					+ padding * 2, image.getHeight());
		} else if (showValue) {
			image.drawString(String.valueOf(value), valueWidth + padding * 2,
					image.getHeight());
		}
		setImage(image);
	}
}
